﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using PloobsEngine;
using Microsoft.Xna.Framework;

namespace Etapa1.SofRender
{
    public enum PrimitivesType
    {
        TriangleList = 0, PointList = 1, PatchWith3ControlPoints = 3
    }

    public enum CullMode
    {
        ClockWise, CounterCloseWise, None
    }

    public class Device 
    {
        public Device() 
        {
            resolution = new Vector2(800 , 600);
            ia = new InputAssembler();
            vs = null;
            ps = null;
            gs = null;
            rasterizer = new Rasterizer();            
            render = new OutputMerger(resolution);            
            br = new BaricentricRasterizer();
        }
        
        InputAssembler ia;
        VertexShader vs;
        GeometricShader gs;
        PixelShader ps;                
        OutputMerger render;
        BaricentricRasterizer br;
        int[] indexBuffer;
        VertexType[] vertices;
        PrimitivesType primitiveType;
        CullMode cullMode = CullMode.CounterCloseWise;
        bool streamOut = false;
        VertexType[] streamOutBuffer;
        Rasterizer rasterizer;
        Vector2 resolution;        

        public bool AlphaEnabled
        {
            get
            {
                return render.AlphaBlending;
            }
            set
            {
                render.AlphaBlending = value;
            }
        }

        public PerformAlphaBlending AlphaFunction
        {
            get
            {
                return render.PerformAlphaBlending;

            }
            set
            {
                render.PerformAlphaBlending = value;
            }
        }

        public Vector2 Resolution
        {
            get { return resolution; }
            set { resolution = value;
                render.Resolution = value;
            }
        }

        public bool DepthBufferTest
        {
            get
            {
                return render.DepthTestEnable;
            }
            set
            {
                render.DepthTestEnable = value;
            }
        }

        public bool DepthBufferWrite
        {
            get
            {
                return render.DepthWrite;
            }
            set
            {
                render.DepthWrite = value;
            }
        }


        public bool StreamOut
        {
            get { return streamOut; }
            set { streamOut = value; }
        }        

        public VertexType[]  GetStreamOutBuffer()
        {
            return this.streamOutBuffer;
        }

        public CullMode CullMode
        {
            get { return cullMode; }
            set { cullMode = value; }
        }

        public Texture2D CreateRenderTarget()
        {
            return new Texture2D(EngineStuff.GraphicsDevice, (int)resolution.X, (int)resolution.Y);
        }

        public void SetEffect(SEffect effect)
        {
            if (effect.PixelShader == null || effect.VertexShader == null)
                throw new Exception("non puedes ser NULO >.< , soh o geometricshader q pode ");

            this.ps = effect.PixelShader;
            this.vs = effect.VertexShader;
            this.gs = effect.GeometricShader;
        }

        public void SetIndexBuffer(int[] indexBuffer)
        {
            this.indexBuffer = indexBuffer;
        }

        public void SetVertexBuffer(VertexType[] vertexBuffer/*, int slot*/)
        {
            this.vertices = vertexBuffer;
        }

        public void DrawIndexedPrimitive(PrimitivesType PrimitiveType)
        {
            primitiveType = PrimitiveType;
            ia.SetData(indexBuffer, vertices);
            ProcessTriangles();
        }

        public void DrawPrimitive(PrimitivesType PrimitiveType)
        {
            primitiveType = PrimitiveType;
            ia.SetData(vertices);
            ProcessTriangles();
        }

        void ProcessTriangles()
        {
            List<VertexType> vertices = new List<VertexType>();

            List<VertexType> vertexShaderProcessed = new List<VertexType>();
            if (primitiveType == PrimitivesType.TriangleList)
            {
                VertexType[] verts = ia.GetTriangle();
                while (verts != null)
                {
                    VertexType v0 = vs.VertexShader(verts[0]);
                    VertexType v1 = vs.VertexShader(verts[1]);
                    VertexType v2 = vs.VertexShader(verts[2]);
                    vertexShaderProcessed.Add(v0);
                    vertexShaderProcessed.Add(v1);
                    vertexShaderProcessed.Add(v2);
                    verts = ia.GetTriangle();
                }
            }
            else
            {
                VertexType verts = ia.GetPoint();
                while (verts != null)
                {
                    VertexType v0 = vs.VertexShader(verts);                    
                    vertexShaderProcessed.Add(v0);                    
                    verts = ia.GetPoint();
                }
            }

            List<VertexType> geometricShaderProcessed = new List<VertexType>();
            List<VertexType> ToRasterizer = new List<VertexType>();
            if (gs != null)
            {                
                for (int i = 0; i < vertexShaderProcessed.Count; )
                {
                    VertexType[] triangleVerts = new VertexType[] { vertexShaderProcessed[i++], vertexShaderProcessed[i++], vertexShaderProcessed[i++] };
                    PrimitivesType outPrimType = primitiveType;
                    triangleVerts = gs.GShader(primitiveType, triangleVerts, ref outPrimType);
                    this.primitiveType = outPrimType;
                    geometricShaderProcessed.AddRange(triangleVerts);
                }

                ToRasterizer = geometricShaderProcessed;

                if (streamOut)
                {
                    this.streamOutBuffer = ToRasterizer.ToArray<VertexType>();
                    return;
                }
            }
            else
            {
                ToRasterizer = vertexShaderProcessed;
                if (streamOut)
                {
                    streamOutBuffer = ToRasterizer.ToArray<VertexType>();
                    return;
                }
            }

            ////Rasterizer
            for (int j = 0; j < ToRasterizer.Count; )
            {
                if (primitiveType == PrimitivesType.TriangleList)
                {                    
                    VertexType v0 = ToRasterizer[j++];
                    VertexType v1 = ToRasterizer[j++];
                    VertexType v2 = ToRasterizer[j++];
                    VertexType[] gsint = new VertexType[] { v0, v1, v2 };

                    ///BASIC (e um pouco errado) Clipping
                    List<VertexType> vclip = new List<VertexType>();
                    for (int i = 0; i < gsint.Count(); )
                    {
                        bool fail = false;
                        Vector4 pos = gsint[i++][Slots.POSITION0];
                        pos = new Vector4(pos.X / pos.W, pos.Y / pos.W, pos.Z / pos.W, pos.W);
                        gsint[i - 1][Slots.POSITION0] = pos;
                        if (pos.X < -1 || pos.X > 1 || pos.Y < -1 || pos.Y > 1 || pos.Z < -1 || pos.Z > 1)
                        {
                            fail = true;
                        }
                
                        pos = gsint[i++][Slots.POSITION0];
                        pos = new Vector4(pos.X / pos.W, pos.Y / pos.W, pos.Z / pos.W, pos.W);
                        gsint[i - 1][Slots.POSITION0] = pos;
                        if (pos.X < -1 || pos.X > 1 || pos.Y < -1 || pos.Y > 1 || pos.Z < -1 || pos.Z > 1)
                        {
                            fail = true;
                        }                

                        pos = gsint[i++][Slots.POSITION0];
                        pos = new Vector4(pos.X / pos.W, pos.Y / pos.W, pos.Z / pos.W, pos.W);
                        gsint[i - 1][Slots.POSITION0] = pos;
                        if (pos.X < -1 || pos.X > 1 || pos.Y < -1 || pos.Y > 1 || pos.Z < -1 || pos.Z > 1)
                        {
                            fail = true;
                        }

                        if (fail == false )
                        {
                            /////BackFacing Culling
                            Vector4 vv0 = gsint[i - 3][Slots.POSITION0];
                            Vector4 vv1 = gsint[i - 2][Slots.POSITION0];
                            Vector4 vv2 = gsint[i - 1][Slots.POSITION0];

                            Vector4 newVariable = vv1 - vv0;
                            Vector3 l0 = new Vector3(newVariable.X, newVariable.Y, newVariable.Z);
                            l0.Normalize();
                            Vector4 newVariable2 = vv2 - vv0;
                            Vector3 l1 = new Vector3(newVariable2.X, newVariable2.Y, newVariable2.Z);
                            l1.Normalize();
                            Vector3 vector = Vector3.Cross(l0, l1);

                            if (vector.Z > 0)
                            {
                                if (cullMode == CullMode.ClockWise || cullMode == CullMode.None)
                                {
                                    vclip.Add(gsint[i - 3]);
                                    vclip.Add(gsint[i - 2]);
                                    vclip.Add(gsint[i - 1]);
                                }
                            }
                            else
                            {
                                if (cullMode == CullMode.CounterCloseWise || cullMode == CullMode.None)
                                {
                                    vclip.Add(gsint[i - 3]);
                                    vclip.Add(gsint[i - 2]);
                                    vclip.Add(gsint[i - 1]);
                                }

                            }
                        }                        
                    }

                    ///Pra coordenadas de tela
                    foreach (var item in vclip)
                    {
                        Vector4 pos = item[Slots.POSITION0];
                        item[Slots.POSITION0] = ToScreenCoordinates(pos);                        
                    }


                    gsint = vclip.ToArray();
                    ///Rasterization
                    if (primitiveType == PrimitivesType.TriangleList)
                    {
                        for (int i = 0; i < gsint.Length; )
                        {
                            Triangle tri = new Triangle(gsint[i++], gsint[i++], gsint[i++]);
                            List<VertexType> drawTriangle = br.DrawTriangle(tri);
                            vertices.AddRange(drawTriangle);
                        }
                    }                                       

                }
                else if (primitiveType == PrimitivesType.PointList) ///NO interpolation nem Cullings .... easy watson
                {
                    VertexType v0 = ToRasterizer[j++];
                    Vector4 pos = v0[Slots.POSITION0];
                    pos = new Vector4(pos.X / pos.W, pos.Y / pos.W, pos.Z / pos.W, pos.W);
                    v0[Slots.POSITION0] = pos;
                    if (pos.X < -1 || pos.X > 1 || pos.Y < -1 || pos.Y > 1 || pos.Z < -1 || pos.Z > 1)
                    {
                        ///OUT NOOB
                    }
                    else
                    {
                        v0[Slots.POSITION0] = ToScreenCoordinates(pos);
                        vertices.Add(v0);
                    }
                }
            }

            
            foreach (var item in vertices)
	        {
                Color pColor = ps.PixelShader(item);
                render.SetPixel((int)item[Slots.POSITION0].X, (int)item[Slots.POSITION0].Y, item[Slots.POSITION0].Z, pColor);                
	        }
           
        }

        Vector4 ToScreenCoordinates(Vector4 pos)
        {
            ///pra [0,1]
            pos.X = (pos.X + 1f) / 2.0f;
            pos.Y = (pos.Y + 1f) / 2.0f;

            if (pos.X > 1 || pos.Y > 1 || pos.X < 0 || pos.Y < 0)
            {
                ///we got a situation
                throw new Exception("BLA");
            }

            if (pos.Z < 0)
            {
                ///we got a situation also
                throw new Exception("BLA");
            }            
            ///pra coordenadas de tela
            pos.X *= (resolution.X - 1);
            pos.Y *= (resolution.Y - 1);
            return pos;
        }

        internal void Draw()
        {
            render.Draw();
        }

        public void Clear(Color color, float depth)
        {
            render.Clear(color, depth);
        }


    }
}
